package cadtoolbox.graphical;



import java.awt.Dimension;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.ConcurrentModificationException;

import javax.swing.JComponent;

import cadtoolbox.model.OligoGraph;

import cadtoolbox.utils.MyPair;


import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.EdgeShape.IndexedRendering;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeArrowRenderingSupport;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.EdgeArrowRenderingSupport;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.transform.LensTransformer;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;

public class OligoRenderer<V, E> implements Renderer<V, E> {

	Renderer.Vertex<V, E> vertexRenderer = new BasicVertexRenderer<V, E>();
	Renderer.VertexLabel<V, E> vertexLabelRenderer = new BasicVertexLabelRenderer<V, E>();
	Renderer.Edge<V, E> edgeRenderer = new BasicEdgeRenderer<V, E>();
	Renderer.EdgeLabel<V, E> edgeLabelRenderer = new BasicEdgeLabelRenderer<V, E>();
	EdgeArrowRenderingSupport edgeArrowRenderingSupport = new BasicEdgeArrowRenderingSupport();

	public void render(RenderContext<V, E> renderContext, Layout<V, E> layout) {

		// paint all the edges
		try {
			for (E e : layout.getGraph().getEdges()) {

				renderEdge(renderContext, layout, e);
				renderEdgeLabel(renderContext, layout, e);
			}
		} catch (ConcurrentModificationException cme) {
			renderContext.getScreenDevice().repaint();
		}

		// paint all the inhibitions
		try {
			OligoGraph<V, E> graph = (OligoGraph<V, E>) layout.getGraph();
			Collection<E> collection = graph.getInhibitions();
			for (E inh : collection) {
				renderInhibition(renderContext, layout, inh);
				// System.out.println(inh.toString());
			}
		} catch (ConcurrentModificationException cme) {
			// renderContext.getScreenDevice().repaint();
		}

		// paint all the vertices
		try {
			for (V v : layout.getGraph().getVertices()) {

				renderVertex(renderContext, layout, v);
				renderVertexLabel(renderContext, layout, v);
			}
		} catch (ConcurrentModificationException cme) {
			renderContext.getScreenDevice().repaint();
		}

	}

	public void renderVertex(RenderContext<V, E> rc, Layout<V, E> layout, V v) {
		vertexRenderer.paintVertex(rc, layout, v);
	}

	public void renderVertexLabel(RenderContext<V, E> rc, Layout<V, E> layout,
			V v) {
		vertexLabelRenderer.labelVertex(rc, layout, v, rc.getVertexLabelTransformer().transform(v));
	}

	public void renderEdge(RenderContext<V, E> rc, Layout<V, E> layout, E e) {
		edgeRenderer.paintEdge(rc, layout, e);
	}

	public void renderInhibition(RenderContext<V, E> rc, Layout<V, E> layout,
			E inh) {
		OligoGraph<V, E> graph = (OligoGraph<V, E>) layout.getGraph();
		MyPair<V, E> pair = graph.getInhibition(inh);
		V v = pair.getLeft();
		E e = pair.getRight();
		Pair<V> endpoints = graph.getEndpoints(inh);
		if(endpoints == null){
			return;
		}
		V v1 = endpoints.getFirst();
		V v2 = endpoints.getSecond();

		GraphicsDecorator g = rc.getGraphicsContext();
		Point2D p = layout.transform(v);
		Point2D p1 = layout.transform(v1);
		Point2D p2 = layout.transform(v2);

		p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
		p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
		p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
		float x = (float) p.getX();
		float y = (float) p.getY();
		float x1 = (float) p1.getX();
		float y1 = (float) p1.getY();
		float x2 = (float) p2.getX();
		float y2 = (float) p2.getY();

		boolean isLoop = v1.equals(v2);
		float endX, endY;
		float finalX, finalY;
		if (isLoop) {
			endX = x1;
			endY = (float) (y1 - 10);
			float dx = endX - x;
			float dy = endY - y;
			float distance = (float) Math.sqrt(dx * dx + dy * dy);
			finalX = endX - 15 * (endX - x) / distance;
			finalY = endY - 15 * (endY - y) / distance;
		} else {
			endX = (x2 + x1) / 2;
			endY = (y2 + y1) / 2;
			float dx = endX - x;
			float dy = endY - y;
			float distance = (float) Math.sqrt(dx * dx + dy * dy);
			finalX = endX - 5 * (endX - x) / distance;
			finalY = endY - 5 * (endY - y) / distance;
		}
		GeneralPath instance = new GeneralPath();
		instance.moveTo(x, y);
		instance.lineTo(finalX, finalY);
		Shape edgeShape = instance;

		instance = new GeneralPath();
		instance.moveTo(-1, 0);
		instance.lineTo(1, 0);
		Shape endShape = instance;
		AffineTransform xform = AffineTransform.getTranslateInstance(finalX, finalY);
		float dx = finalX - x;
		float dy = finalY - y;
		float thetaRadians = (float) Math.atan2(dy, dx);
		xform.rotate(thetaRadians+Math.PI/2);
		xform.scale(4, 0.0);
		endShape = xform.createTransformedShape(endShape);
		
		Paint oldPaint = g.getPaint();

		// get Paints for filling and drawing
		// (filling is done first so that drawing and label use same Paint)
		Paint fill_paint = rc.getEdgeFillPaintTransformer().transform(e);
		if (fill_paint != null) {
			g.setPaint(fill_paint);
			g.fill(edgeShape);
			g.fill(endShape);
		}
		Paint draw_paint = rc.getEdgeDrawPaintTransformer().transform(e);
		if (draw_paint != null) {
			g.setPaint(draw_paint);
			g.draw(edgeShape);
			g.draw(endShape);
		}

		// restore old paint
		g.setPaint(oldPaint);

	}

	protected void drawSimpleEdge(RenderContext<V, E> rc, Layout<V, E> layout,
			E e) {}

	public void renderEdgeLabel(RenderContext<V, E> rc, Layout<V, E> layout, E e) {
		edgeLabelRenderer.labelEdge(rc, layout, e, rc.getEdgeLabelTransformer()
				.transform(e));
	}

	public void setVertexRenderer(Renderer.Vertex<V, E> r) {
		this.vertexRenderer = r;
	}

	public void setEdgeRenderer(Renderer.Edge<V, E> r) {
		this.edgeRenderer = r;
	}

	/**
	 * @return the edgeLabelRenderer
	 */
	public Renderer.EdgeLabel<V, E> getEdgeLabelRenderer() {
		return edgeLabelRenderer;
	}

	/**
	 * @param edgeLabelRenderer
	 *            the edgeLabelRenderer to set
	 */
	public void setEdgeLabelRenderer(Renderer.EdgeLabel<V, E> edgeLabelRenderer) {
		this.edgeLabelRenderer = edgeLabelRenderer;
	}

	/**
	 * @return the vertexLabelRenderer
	 */
	public Renderer.VertexLabel<V, E> getVertexLabelRenderer() {
		return vertexLabelRenderer;
	}

	/**
	 * @param vertexLabelRenderer
	 *            the vertexLabelRenderer to set
	 */
	public void setVertexLabelRenderer(
			Renderer.VertexLabel<V, E> vertexLabelRenderer) {
		this.vertexLabelRenderer = vertexLabelRenderer;
	}

	/**
	 * @return the edgeRenderer
	 */
	public Renderer.Edge<V, E> getEdgeRenderer() {
		return edgeRenderer;
	}

	/**
	 * @return the vertexRenderer
	 */
	public Renderer.Vertex<V, E> getVertexRenderer() {
		return vertexRenderer;
	}

}